package com.sh.data.engine.common.util;

import com.google.common.collect.Lists;
import com.sh.data.engine.common.enumDefinition.TaskScheduleCycle;
import org.apache.commons.lang3.math.NumberUtils;

import java.util.Arrays;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
import java.util.stream.Collectors;

/**
 * @author tyf @Description:
 * @date 2020-06-1910:14
 */
public class ScheduleCycleUtil {

    public static Date getNextExecutionTime(
        int cycle,
        String executionPoint,
        Date effectiveDateStart,
        Date effectiveDateEnd,
        Date lastExecutionTime,
        Integer startTimeHour,
        Integer startTimeMin,
        Integer endTimeHour,
        Integer endTimeMin) {

        // 月调度计算下次执行周期
        if (cycle == TaskScheduleCycle.month.getKey()) {

            return computeMonthSchedule(
                executionPoint,
                effectiveDateStart,
                effectiveDateEnd,
                lastExecutionTime,
                startTimeHour,
                startTimeMin);

            // 周调度计算下次执行周期
        } else if (cycle == TaskScheduleCycle.week.getKey()) {

            return computeWeekSchedule(
                executionPoint,
                effectiveDateStart,
                effectiveDateEnd,
                lastExecutionTime,
                startTimeHour,
                startTimeMin);

            // 天调度计算下次执行周期
        } else if (cycle == TaskScheduleCycle.day.getKey()) {

            return computeDaySchedule(
                effectiveDateStart, effectiveDateEnd, lastExecutionTime, startTimeHour, startTimeMin);

            // 小时调度计算下次执行周期
        } else if (cycle == TaskScheduleCycle.hour.getKey()) {

            return computeHourSchedule(
                executionPoint,
                effectiveDateStart,
                effectiveDateEnd,
                lastExecutionTime,
                startTimeHour,
                startTimeMin,
                endTimeHour,
                endTimeMin);

            // 分钟调度计算下次执行周期
        } else if (cycle == TaskScheduleCycle.minute.getKey()) {

            return computeMinuteSchedule(
                executionPoint,
                effectiveDateStart,
                effectiveDateEnd,
                lastExecutionTime,
                startTimeHour,
                startTimeMin,
                endTimeHour,
                endTimeMin);
        }

        return null;
    }

    public static String generateCronExp(
        int cycle,
        String executionPoint,
        Integer startTimeHour,
        Integer startTimeMin,
        Integer endTimeHour,
        Integer endTimeMin) {
        CronBuilder cronBuilder = CronBuilder.builder();
        switch (cycle) {
            case 0:
                List<String> strings = Arrays.asList(executionPoint.split(","));
                List<Integer> dayOfMonth =
                    strings.stream().map(s -> Integer.parseInt(s)).collect(Collectors.toList());
                cronBuilder.dayOfMonth(dayOfMonth.toArray(new Integer[dayOfMonth.size()]));
                cronBuilder.hours(startTimeHour).minutes(startTimeMin);
                break;
            case 1:
                List<String> execPointDoWStr = Arrays.asList(executionPoint.split(","));
                List<Integer> dayOfWeek =
                    execPointDoWStr.stream().map(s -> Integer.parseInt(s)).collect(Collectors.toList());
                cronBuilder.dayOfWeek(dayOfWeek.toArray(new Integer[dayOfWeek.size()]));
                cronBuilder.hours(startTimeHour).minutes(startTimeMin);
                break;
            case 2:
                cronBuilder.hours(startTimeHour).minutes(startTimeMin);
                break;
            case 3:
                List<Integer> hours = Lists.newArrayList();
                endTimeHour = endTimeMin >= startTimeMin ? endTimeHour : endTimeHour - 1;
                for (int i = startTimeHour; i <= endTimeHour; i = i + Integer.parseInt(executionPoint)) {
                    hours.add(i);
                }
                cronBuilder.hours(hours.toArray(new Integer[hours.size()])).minutes(startTimeMin);
                break;
            case 4:
                // TODO 周期为分钟的需要 修改 xxl对应的调度策略
                break;
        }
        return cronBuilder.build();
    }

    /**
     * 按分钟计算下个执行时间
     *
     * @param executionPointStr
     * @param effectiveDateStart
     * @param effectiveDateEnd
     * @param lastExecutionTime
     * @param startTimeHour
     * @param startTimeMin
     * @param endTimeHour
     * @param endTimeMin
     * @return
     */
    private static Date computeMinuteSchedule(
        String executionPointStr,
        Date effectiveDateStart,
        Date effectiveDateEnd,
        Date lastExecutionTime,
        int startTimeHour,
        int startTimeMin,
        int endTimeHour,
        int endTimeMin) {

        int executionPoint = Integer.parseInt(executionPointStr);

        Calendar nextTime = Calendar.getInstance();
        // 计划每天执行日期
        Date current = new Date();
        nextTime.setTime(current);
        nextTime.set(Calendar.HOUR_OF_DAY, startTimeHour);
        nextTime.set(Calendar.MINUTE, startTimeMin);
        nextTime.set(Calendar.SECOND, 0);
        nextTime.set(Calendar.MILLISECOND, 0);

        // 计划每天截止日期
        Calendar end = Calendar.getInstance();
        end.setTime(nextTime.getTime());
        end.set(Calendar.HOUR_OF_DAY, endTimeHour);
        end.set(Calendar.MINUTE, endTimeMin);
        end.set(Calendar.SECOND, 0);
        end.set(Calendar.MILLISECOND, 0);

        if (lastExecutionTime != null) {

            if (nextTime.getTimeInMillis() <= lastExecutionTime.getTime()) {
                // 递归加一个时间间隔 直到大于当前时间
                addMinute(nextTime, lastExecutionTime, executionPoint);

                if (nextTime.getTimeInMillis() == lastExecutionTime.getTime()) {
                    nextTime.add(Calendar.MINUTE, executionPoint);
                }

                end.setTime(nextTime.getTime());
                end.set(Calendar.HOUR_OF_DAY, endTimeHour);
                end.set(Calendar.MINUTE, endTimeMin);
                end.set(Calendar.SECOND, 0);
                end.set(Calendar.MILLISECOND, 0);

                // 超过当天的计划执行截止日期加1天
                if (nextTime.getTimeInMillis() > end.getTimeInMillis()) {
                    nextTime.add(Calendar.MINUTE, -executionPoint);
                    nextTime.set(Calendar.HOUR_OF_DAY, startTimeHour);
                    nextTime.set(Calendar.MINUTE, startTimeMin);
                    nextTime.add(Calendar.DATE, 1);
                }
            }
        }

        // 如果下次执行时间小于当前时间 只能下个月执行
        if (nextTime.getTimeInMillis() < current.getTime()) {

            // 递归加一个时间间隔 直到大于当前时间
            addMinute(nextTime, current, executionPoint);

            if (nextTime.getTimeInMillis() > end.getTimeInMillis()) {
                nextTime.add(Calendar.MINUTE, -executionPoint);
                nextTime.set(Calendar.HOUR_OF_DAY, startTimeHour);
                nextTime.set(Calendar.MINUTE, startTimeMin);
                nextTime.add(Calendar.DATE, 1);
            }
        }

        // 如果下次执行日期小于有效开始日期
        if (nextTime.getTimeInMillis() < effectiveDateStart.getTime()) {

            nextTime.setTime(effectiveDateStart);
            nextTime.set(Calendar.HOUR_OF_DAY, startTimeHour);
            nextTime.set(Calendar.MINUTE, startTimeMin);
            nextTime.set(Calendar.SECOND, 0);
            nextTime.set(Calendar.MILLISECOND, 0);
        }

        Date nextExecutionTime = nextTime.getTime();
        // 下个计划执行日期超过有效期 则该任务不需要再执行
        if (nextExecutionTime.getTime() > effectiveDateEnd.getTime()) {
            return null;
        }
        return nextTime.getTime();
    }

    private static void addMinute(Calendar nextTime, Date date, int min) {

        if (nextTime.getTimeInMillis() >= date.getTime()) {
            return;
        }

        nextTime.add(Calendar.MINUTE, min);

        addMinute(nextTime, date, min);
    }

    /**
     * 按小时计算下个执行时间
     *
     * @param executionPointStr
     * @param effectiveDateStart
     * @param effectiveDateEnd
     * @param lastExecutionTime
     * @param startTimeHour
     * @param startTimeMin
     * @param endTimeHour
     * @param endTimeMin
     * @return
     */
    private static Date computeHourSchedule(
        String executionPointStr,
        Date effectiveDateStart,
        Date effectiveDateEnd,
        Date lastExecutionTime,
        int startTimeHour,
        int startTimeMin,
        int endTimeHour,
        int endTimeMin) {

        int executionPoint = Integer.parseInt(executionPointStr);

        Calendar nextTime = Calendar.getInstance();
        // 计划每天执行日期
        Date current = new Date();
        nextTime.setTime(current);
        nextTime.set(Calendar.HOUR_OF_DAY, startTimeHour);
        nextTime.set(Calendar.MINUTE, startTimeMin);
        nextTime.set(Calendar.SECOND, 0);
        nextTime.set(Calendar.MILLISECOND, 0);

        // 计划每天截止日期
        Calendar end = Calendar.getInstance();
        end.setTime(nextTime.getTime());
        end.set(Calendar.HOUR_OF_DAY, endTimeHour);
        end.set(Calendar.MINUTE, endTimeMin);
        end.set(Calendar.SECOND, 0);
        end.set(Calendar.MILLISECOND, 0);

        if (lastExecutionTime != null) {

            if (nextTime.getTimeInMillis() <= lastExecutionTime.getTime()) {
                // 递归加一个时间间隔 直到大于当前时间
                addHour(nextTime, lastExecutionTime, executionPoint);

                if (nextTime.getTimeInMillis() == lastExecutionTime.getTime()) {
                    nextTime.add(Calendar.HOUR_OF_DAY, executionPoint);
                }

                end.setTime(nextTime.getTime());
                end.set(Calendar.HOUR_OF_DAY, endTimeHour);
                end.set(Calendar.MINUTE, endTimeMin);
                end.set(Calendar.SECOND, 0);
                end.set(Calendar.MILLISECOND, 0);

                // 超过当天的计划执行截止日期加1天
                if (nextTime.getTimeInMillis() > end.getTimeInMillis()) {
                    nextTime.add(Calendar.HOUR_OF_DAY, -executionPoint);
                    nextTime.set(Calendar.HOUR_OF_DAY, startTimeHour);
                    nextTime.set(Calendar.MINUTE, startTimeMin);
                    nextTime.add(Calendar.DATE, 1);
                }
            }
        }

        // 如果下次执行时间小于当前时间 只能下个月执行
        if (nextTime.getTimeInMillis() < current.getTime()) {

            // 递归加一个时间间隔 直到大于当前时间
            addHour(nextTime, current, executionPoint);

            if (nextTime.getTimeInMillis() > end.getTimeInMillis()) {
                nextTime.add(Calendar.HOUR_OF_DAY, -executionPoint);
                nextTime.set(Calendar.HOUR_OF_DAY, startTimeHour);
                nextTime.set(Calendar.MINUTE, startTimeMin);
                nextTime.add(Calendar.DATE, 1);
            }
        }

        // 如果下次执行日期小于有效开始日期
        if (nextTime.getTimeInMillis() < effectiveDateStart.getTime()) {

            nextTime.setTime(effectiveDateStart);
            nextTime.set(Calendar.HOUR_OF_DAY, startTimeHour);
            nextTime.set(Calendar.MINUTE, startTimeMin);
            nextTime.set(Calendar.SECOND, 0);
            nextTime.set(Calendar.MILLISECOND, 0);
        }

        Date nextExecutionTime = nextTime.getTime();
        // 下个计划执行日期超过有效期 则该任务不需要再执行
        if (nextExecutionTime.getTime() > effectiveDateEnd.getTime()) {
            return null;
        }
        return nextTime.getTime();
    }

    private static void addHour(Calendar nextTime, Date date, int hour) {

        if (nextTime.getTimeInMillis() >= date.getTime()) {
            return;
        }

        nextTime.add(Calendar.HOUR_OF_DAY, hour);

        addHour(nextTime, date, hour);
    }

    /**
     * 按天计算下个执行时间
     *
     * @param effectiveDateStart
     * @param effectiveDateEnd
     * @param lastExecutionTime
     * @param startTimeHour
     * @param startTimeMin
     * @return
     */
    private static Date computeDaySchedule(
        Date effectiveDateStart,
        Date effectiveDateEnd,
        Date lastExecutionTime,
        int startTimeHour,
        int startTimeMin) {

        Calendar nextTime = Calendar.getInstance();

        // 获取计划执行的日期
        Date current = new Date();

        nextTime.setTime(current);

        nextTime.set(Calendar.HOUR_OF_DAY, startTimeHour);
        nextTime.set(Calendar.MINUTE, startTimeMin);
        nextTime.set(Calendar.SECOND, 0);
        nextTime.set(Calendar.MILLISECOND, 0);

        // 如果不是第一次执行 需要判断最近一次执行的时间 与下次执行的时间
        if (lastExecutionTime != null) {

            // 如果下次计划执行日期小于等于上次执行的日期 则将时间设置成上次执行的年月
            if (nextTime.getTimeInMillis() <= lastExecutionTime.getTime()) {
                nextTime.setTime(lastExecutionTime);
                nextTime.set(Calendar.HOUR_OF_DAY, startTimeHour);
                nextTime.set(Calendar.MINUTE, startTimeMin);
                nextTime.set(Calendar.SECOND, 0);
                nextTime.set(Calendar.MILLISECOND, 0);
            }

            // 如果还是小于上次执行时间 则加一个周
            if (nextTime.getTimeInMillis() <= lastExecutionTime.getTime()) {
                nextTime.add(Calendar.DATE, 1);
            }
        }

        // 如果下次执行时间小于当前时间
        if (nextTime.getTimeInMillis() < current.getTime()) {
            nextTime.add(Calendar.DATE, 1);
        }

        // 如果下次执行日期小于有效开始日期
        if (nextTime.getTimeInMillis() < effectiveDateStart.getTime()) {

            nextTime.setTime(effectiveDateStart);
            nextTime.set(Calendar.HOUR_OF_DAY, startTimeHour);
            nextTime.set(Calendar.MINUTE, startTimeMin);
            nextTime.set(Calendar.SECOND, 0);
            nextTime.set(Calendar.MILLISECOND, 0);

            // 如果计划开始日期在同一个月后还比有效开始日期小则加一个月
            if (nextTime.getTimeInMillis() < effectiveDateStart.getTime()) {
                nextTime.add(Calendar.DATE, 1);
            }
        }

        Date nextExecutionTime = nextTime.getTime();
        // 下个计划执行日期超过有效期 则该任务不需要再执行
        if (nextExecutionTime.getTime() > effectiveDateEnd.getTime()) {
            return null;
        }
        return nextTime.getTime();
    }

    /**
     * 按周计算下个执行时间
     *
     * @param executionPointStr
     * @param effectiveDateStart
     * @param effectiveDateEnd
     * @param startTime
     * @param startTimeHour
     * @param startTimeMin
     * @return
     */
    private static Date computeWeekSchedule(
        String executionPointStr,
        Date effectiveDateStart,
        Date effectiveDateEnd,
        Date startTime,
        int startTimeHour,
        int startTimeMin) {

        Date current = new Date();

        if (!current.after(effectiveDateStart)) {
            current = effectiveDateStart;
        }

        if (startTime == null || !startTime.after(current)) {
            startTime = current;
        }

        Calendar nextTime = Calendar.getInstance();
        nextTime.setTime(startTime);
        nextTime.set(Calendar.HOUR_OF_DAY, startTimeHour);
        nextTime.set(Calendar.MINUTE, startTimeMin);
        nextTime.set(Calendar.SECOND, 0);
        nextTime.set(Calendar.MILLISECOND, 0);

        int dayOfWeek = nextTime.get(Calendar.DAY_OF_WEEK);
        // 如果当前时间是周日则减去一天 时间变为周六 因为周六是一周的最后一天
        if (dayOfWeek == 1) {
            nextTime.add(Calendar.DAY_OF_WEEK, -1);
        }

        String[] days = executionPointStr.split(",");
        String firstDay = "";
        for (int i = 0; i < days.length; i++) {
            if (i == 0) {
                firstDay = days[i];
            }
            // 如果是周一到周六
            if (Integer.valueOf(days[i]) < 7) {
                nextTime.set(Calendar.DAY_OF_WEEK, Integer.valueOf(days[i]) + 1);
            } else {
                // 如果是周日 设置成周六并且+1天
                nextTime.set(Calendar.DAY_OF_WEEK, 7);
                nextTime.add(Calendar.DAY_OF_WEEK, 1);
            }

            if (nextTime.getTime().after(current) && nextTime.getTime().after(startTime)) {
                break;
            }
        }

        if (!nextTime.getTime().after(current) || !nextTime.getTime().after(startTime)) {
            nextTime.add(Calendar.DAY_OF_WEEK, 7);
            dayOfWeek = nextTime.get(Calendar.DAY_OF_WEEK);
            // 如果当前时间是周日则减去一天 时间变为周六 因为周六是一周的最后一天
            if (dayOfWeek == 1) {
                nextTime.add(Calendar.DAY_OF_WEEK, -1);
            }
            // 如果是周一到周六
            if (Integer.valueOf(firstDay) < 7) {
                nextTime.set(Calendar.DAY_OF_WEEK, Integer.valueOf(firstDay) + 1);
            } else {
                // 如果是周日 设置成周六并且+1天
                nextTime.set(Calendar.DAY_OF_WEEK, 7);
                nextTime.add(Calendar.DAY_OF_WEEK, 1);
            }
        }

        Date NextExecutionTime = nextTime.getTime();
        // 下个计划执行日期超过有效期 则该任务不需要再执行
        if (null != effectiveDateEnd && NextExecutionTime.getTime() > effectiveDateEnd.getTime()) {
            return null;
        }
        return nextTime.getTime();
    }

    /**
     * 按月计算下个执行周期
     *
     * @param executionPoint
     * @param effectiveDateStart
     * @param effectiveDateEnd
     * @param startTime
     * @param startTimeHour
     * @param startTimeMin
     * @return
     */
    private static Date computeMonthSchedule(
        String executionPoint,
        Date effectiveDateStart,
        Date effectiveDateEnd,
        Date startTime,
        int startTimeHour,
        int startTimeMin) {

        Date current = new Date();

        if (!current.after(effectiveDateStart)) {
            current = effectiveDateStart;
        }

        if (startTime == null || !startTime.after(current)) {
            startTime = current;
        }

        Calendar nextTime = Calendar.getInstance();
        nextTime.setTime(startTime);
        nextTime.set(Calendar.HOUR_OF_DAY, startTimeHour);
        nextTime.set(Calendar.MINUTE, startTimeMin);
        nextTime.set(Calendar.SECOND, 0);
        nextTime.set(Calendar.MILLISECOND, 0);

        int maxDay = nextTime.getActualMaximum(Calendar.DAY_OF_MONTH);

        String[] days = executionPoint.split(",");

        String firstDay = "";

        for (int i = 0; i < days.length; i++) {

            if (i == 0) {
                firstDay = days[i];
            }

            if (NumberUtils.isNumber(days[i])) {
                if (maxDay <= Integer.valueOf(days[i])) {
                    nextTime.set(Calendar.DAY_OF_MONTH, maxDay);
                } else {
                    nextTime.set(Calendar.DAY_OF_MONTH, Integer.valueOf(days[i]));
                }
            } else {
                nextTime.set(Calendar.DAY_OF_MONTH, maxDay);
            }

            if (nextTime.getTime().after(current) && nextTime.getTime().after(startTime)) {
                break;
            }
        }

        if (!nextTime.getTime().after(current) || !nextTime.getTime().after(startTime)) {
            nextTime.set(Calendar.DAY_OF_MONTH, 1);
            nextTime.add(Calendar.MONTH, 1);
            maxDay = nextTime.getActualMaximum(Calendar.DAY_OF_MONTH);
            if (NumberUtils.isNumber(firstDay)) {
                if (maxDay <= Integer.valueOf(firstDay)) {
                    nextTime.set(Calendar.DAY_OF_MONTH, maxDay);
                } else {
                    nextTime.set(Calendar.DAY_OF_MONTH, Integer.valueOf(firstDay));
                }
            } else {
                nextTime.set(Calendar.DAY_OF_MONTH, maxDay);
            }
        }

        Date NextExecutionTime = nextTime.getTime();
        // 下个计划执行日期超过有效期 则该任务不需要再执行
        if (null != effectiveDateEnd && NextExecutionTime.getTime() > effectiveDateEnd.getTime()) {
            return null;
        }
        return nextTime.getTime();
    }

    //  public static void main(String[] args) {
    //
    //    Date effectiveDateStart =
    //        new Date(DateUtil.toLong("2018-02-02 00:00:00.000", "yyyy-MM-dd HH:mm:ss.SSS"));
    //    Date effectiveDateEnd =
    //        new Date(DateUtil.toLong("2019-12-01 23:59:59.999", "yyyy-MM-dd HH:mm:ss.SSS"));
    //    //            Date lastExecutionTime = null;
    //
    //    Date lastExecutionTime =
    //        new Date(DateUtil.toLong("2019-01-14 18:07:00.000", "yyyy-MM-dd HH:mm:ss.SSS"));
    //
    //    int cycle = 4;
    //    int executionPoint = 5;
    //    int startTimeHour = 0;
    //    int startTimeMin = 55;
    //    int endTimeHour = 18;
    //    int endTimeMin = 8;
    //
    //    //    for (int i = 1; i <= 7; i++) {
    //    Date date =
    //        getNextExecutionTime(
    //            cycle,
    //            executionPoint,
    //            effectiveDateStart,
    //            effectiveDateEnd,
    //            lastExecutionTime,
    //            startTimeHour,
    //            startTimeMin,
    //            endTimeHour,
    //            endTimeMin);
    //
    //    System.out.println(DateUtil.formatDate(date, "yyyy-MM-dd HH:mm:ss.SSS"));
    //    //    }
    //  }
}
